package fileevent

import (
	"github.com/fsnotify/fsnotify"
	"github.com/golang/glog"
	"context"
	"os"
	"path/filepath"
	"encoding/json"
	"cloud_worm/worm/client/monitor/kafka"
	"cloud_worm/worm/client/monitor/monitorlog"
	"sync"
	"bytes"
	"strings"
	"github.com/astaxie/beego/logs"
)

// 需要回收的资源有：
// watcher
// producer
// Goroutine
type FileMonitor struct {
	ignoreSet		map[string]struct{}
	fileRules 		map[string][]string				// filename----white list
	mux  			sync.Mutex

	watcher 		*fsnotify.Watcher
	producer 		kafka.KafProducer
	logFd 			*logs.BeeLogger

	localIP 		string
	cancel 			context.CancelFunc
}

func NewFileMonitor(localIP string, producer kafka.KafProducer, logFd *logs.BeeLogger, cancel context.CancelFunc) *FileMonitor {
	w, err := fsnotify.NewWatcher()
	if err != nil {
		glog.Errorf(err.Error())
		return nil
	}

	return &FileMonitor {
		watcher: w,
		ignoreSet: make(map[string]struct{}),
		fileRules: make(map[string][]string),

		localIP: localIP,
		producer: producer,
		logFd: logFd,
		cancel: cancel,
	}
}

func (fs *FileMonitor) Start(localIP string, producer kafka.KafProducer, logFd *logs.BeeLogger, cancel context.CancelFunc) error {
	w, err := fsnotify.NewWatcher()
	if err != nil {
		glog.Errorf(err.Error())
		return err
	}

	fs.watcher = w
	fs.ignoreSet = make(map[string]struct{})
	fs.fileRules = make(map[string][]string)
	fs.localIP = localIP
	fs.producer = producer
	fs.logFd = logFd
	fs.cancel = cancel
	return nil
}

func (fs *FileMonitor) Close() {
	// 取消相关的协程资源，触发整个资源的回收处理
	fs.cancel()
	return
}

func (fs *FileMonitor) Loop(ctx context.Context) error {
	go func(fs *FileMonitor) {
		glog.Infof("file_loop start for %s", fs.localIP)

		var sep = []byte("")
		var msgFlag = []byte("afocus_filelog")
		sl := make([][]byte, 0, 2)
		sl = append(sl, msgFlag)

		for {
			select {
			case ev := <- fs.watcher.Events:
				if ev.Op & fsnotify.Create == fsnotify.Create {
					fi, err := os.Stat(ev.Name)
					if err == nil && fi.IsDir() {
						err = fs.watcher.Add(ev.Name)
						if err != nil {
							glog.Errorf("new dir'%s' add watch error: %s", err.Error())
						}
					}
				} else if ev.Op & fsnotify.Write == fsnotify.Write {

				} else if ev.Op & fsnotify.Remove == fsnotify.Remove {
					fi, err := os.Stat(ev.Name)
					if err == nil && fi.IsDir() {
						fs.watcher.Remove(ev.Name)
					}
				} else if ev.Op & fsnotify.Rename == fsnotify.Rename {
					fs.watcher.Add(ev.Name)
				} else if ev.Op & fsnotify.Chmod == fsnotify.Chmod {

				}

				// TODO: 白名单处理
				if strings.Contains(ev.Name, ".#") {
					// 不做处理
				} else {
					if _, exist := fs.ignoreSet[ev.Name]; exist == false {
						fEvent := &FileEvent{}
						if err := fEvent.Analyzer(fs.localIP, ev); err != nil {
							// TODO: 暂时先不用处理
						} else {
							if b, err := fEvent.MarshalJSON(); err == nil {
								if fs.logFd != nil {
									// 写日志
									fs.logFd.Info("%s: %s", monitorlog.MonitorFileLog, b)
								}
								if fs.producer != nil {
									// 写kafka
									sl = sl[:1]
									sl = append(sl, b)
									fs.producer.AsyncSend(bytes.Join(sl, sep), "")
								}
							}
						}
					}
				}

			case err := <- fs.watcher.Errors:
				glog.Errorf("file monitor loop error: %s", err.Error())
				// 关闭句柄资源
				fs.watcher.Close()
				return
			case <- ctx.Done():
				glog.Errorf("server Done stop file monitor loop ......")
				// 关闭句柄资源
				fs.watcher.Close()
				return
			}
		}
		glog.Errorf("file_loop end for %s", fs.localIP)
	}(fs)

	return nil
}

// 以下为规则相关的操作
func (fs *FileMonitor) add(fileName string, white ...string) error {
	fullWhite := make([]string, 0, len(white))
	ignoreSet := make([]string, 0, len(white))
	for _, item := range white {
		fullName := filepath.Join(fileName, item)
		fInfo, err := os.Stat(fullName)
		if err != nil || fInfo.IsDir() == false {
			ignoreSet = append(ignoreSet, fullName)
		} else {
			fullWhite = append(fullWhite, fullName)
		}
	}

	fs.mux.Lock()
	for _, item := range ignoreSet {
		fs.ignoreSet[item] = struct{}{}
	}
	fs.fileRules[fileName] = fullWhite
	fs.mux.Unlock()

	fInfo, err := os.Stat(fileName)
	if err != nil {
		return err
	}
	if fInfo.IsDir() == false {
		return fs.watcher.Add(fileName)
	}

	filepath.Walk(fileName, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			for _, item := range fullWhite {
				if path == item {
					return nil
				}
			}

			err = fs.watcher.Add(path)
			if err != nil {
				return err
			}
		}
		return nil
	})

	return nil
}

func (fs *FileMonitor) remove(fileName string) error {
	fs.mux.Lock()
	delete(fs.fileRules, fileName)
	fs.mux.Unlock()

	fInfo, err := os.Stat(fileName)
	if err != nil {
		fs.watcher.Remove(fileName)
		return err
	}
	if fInfo.IsDir() == false {
		return fs.watcher.Remove(fileName)
	}

	filepath.Walk(fileName, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			fs.watcher.Remove(path)
			return nil
		}
		return nil
	})

	return nil
}

func (fs *FileMonitor) clear() error {
	fs.mux.Lock()
	defer fs.mux.Unlock()

	// 去除监听
	for k, _ := range fs.fileRules {
		fInfo, err := os.Stat(k)
		if err != nil {
			// 文件信息获取失败，直接remove
			fs.watcher.Remove(k)
			continue
		}

		if fInfo.IsDir() == false {
			// 普通文件，直接Remove
			fs.watcher.Remove(k)
			continue
		}

		filepath.Walk(k, func(path string, info os.FileInfo, err error) error {
			if info.IsDir() {
				fs.watcher.Remove(path)
				return nil
			}
			return nil
		})
	}
	// 规则清空
	fs.ignoreSet = make(map[string]struct{})
	fs.fileRules = make(map[string][]string)
	return nil
}

func (fs *FileMonitor) RuleString() string {
	fs.mux.Lock()
	defer fs.mux.Unlock()

	data, err := json.Marshal(fs.fileRules)
	if err != nil {
		return ""
	}
	return string(data)
}

// FileMonitor.UpdateFileRule 不能使用锁
func (fs *FileMonitor) UpdateFileRule(newRules map[string][]string) error{
	fs.clear()

	for k, v := range newRules {
		if err := fs.add(k, v...); err != nil {
			glog.Errorf("file monitor add file'%s' error: %s", k, err.Error())
		}
	}
	return nil
}